#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>




//内置类型：int,short,long,float,double,boolean
//自定义类型：结构体

//结构体的声明
//struct tag//(tag 为标签名)
//{
//	char name;
//	//member - list;(成员变量，能有1个或者多个)
//}//variable-list(变量列表);
//

//例如描述一个学生

//struct Stu
//{
//	char name[20];//名字
//	int id[20];//学号
//	char sex[5];//性别
//};//注意分号不能丢


//struct Book
//{
//	char name[20];//书名
//	char author[20];//作者
//	float price;//价格
//	char id[13];//书号
//}b1, b2;//这里创建的是全局变量
//
//
//struct Stu b3, b4;//这里创建的是全局变量
//
//int main()
//{
//	struct Book b5, b6;//这里创建的是局部变量
//	
//	return 0;
//}

//初始化
//struct Book
//{
//	char name[20];//书名
//	char author[20];//作者
//	char id[13];//书号
//}b1 = {.author = "zhangsan", .name = "c语言", .id = "23130430"};
//
//
////int main()
////{
////	struct Book b1 = { "c语言", "zhangsan", "23130423"};
////	printf("%s\n%s\n%s\n", b1.name, b1.author, b1.id);
////
////	return 0;
////}
//
////int main()
////{
////	struct Book b1 = { .author = "zhangsan", .id = "23130423", .name = "c语言" };
////	printf("%s\n%s\n%s", b1.author, b1.name, b1.id);
////	return 0;
////}
//
//int main()
//{
//	printf("%s\n%s\n%s\n", b1.author, b1.id, b1.name);
//	return 0;
//}


//匿名结构体类型
//在声明结构的时候，可以不完全的声明

//struct
//{
//	int a;
//	char b;
//	float c;
//}x = { .a = 20, .b = 'c', .c = 2.3f};//但是必须在创建结构体之后马上创建一个结构体变量，且只能使用一次
//int main()
//{
//	printf("%d\n%c\n%f\n", x.a, x.b, x.c);
//

//
//struct
//{
//	char c;
//	int i;
//	double d;
//}s;
//
//struct
//{
//	char c;
//	int i;
//	double d;
//}*ps;
//
//int main()
//{
//	ps = &s;//非法 编译器会将上面两个声明当成完全不同的两个类型，所以是非法的
//	        //匿名的结构体类型，如果没有对结构体类型重命名的话，基本上只能使用一次
//	return 0;
//}

//结构体的重命名

//typedef struct
//{
//	char c;
//	int i;
//	double d;
//}S;
//
//int main()
//{
//	S s;//正确
//
//	return 0;
//}

//链表
//分为数据域和指针域

//结构体的子引用
//结构体中包含一个类型为该结构体本身的成员是否可以呢？
//struct Node
//{
//	int data;
//	struct Node next;//不可以，一个结构体中再包含一个同类型的结构体变量，这样结构体变量的大小就会无穷大，不合理
//};

//正确的自引用的方式
//struct Node
//{
//	int data;//数据
//	struct Node* next;//指针
//
//};

//重命名

//typedef struct Node
//{
//	int data;//数据
//	struct Node* next;//指针
//}Node;

//struct Node
//{
//	int data;
//	struct Node* next;
//};
//
//typedef struct Node Node;
//
//int main()
//{
//	Node n1;
//	return; 
//}

//typedef对匿名结构体类型重命名
//以下方式是错误的，因为Node是对前面的匿名结构体类型的重命名产生的，但是再匿名结构体内部提前使用Node类型来创建成员变量，这是不行的
//typedef struct
//{
//	int data;
//	Node* next;//err
//}Node;
//
//int main()
//{
//
//	return 0;
//}

//struct S
//{
//	char c1;//1
//	int i;//4
//	char c2;//1
//};
//
//int main()
//{
//	struct S s = { 0 };
//	printf("%zd\n", sizeof(s));//为什么是12？？？
//
//	return 0;
//}

//int main()
//{
//	printf("%zd\n", sizeof(double));
//
//	return 0;
//}



//结构体的内存对齐
//首先需要掌握结构体的对齐规则
// 1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
// 2.其他成员变量,要对齐到(对齐数)的整数倍的地址处
// 对齐数 = 编译器默认的一个对齐数 与 该成员变量大小 的 较小值
// vs中默认的对齐数是8
// 3.结构体总大小为最大对齐数的整数倍
// 最大对齐数:结构体中每个成员变量都有一个对齐数,所有对齐数中最大的,就是最大对齐数
// 4.如果嵌套了结构体的情况:嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍
// 结构体的正数大小就是所有最大对齐数(包含嵌套结构体中成员的对齐数)的整数倍.

//练习1

//struct S1
//{
//	char c1;
//	int i;
//	char c2;
//};

//struct S2
//{
//	char c1;
//	char c2;
//	int i;
//};

//struct S3
//{
//	double d;
//	char c;
//	int i;
//};
//
//struct S4
//{
//	char c1;
//	struct S3 s3;
//	double d;
//
//};
//int main()
//{
	//printf("%zd\n", sizeof(struct S1));

	//printf("%zd\n", sizeof(struct S2));

	//printf("%zd\n", sizeof(struct S3));

	//printf("%zd\n", sizeof(struct S4));


	//return 0;
//}





//为什么会存在内存对齐???
//1.平台（移植）原因
//不是所有的硬件平台都能访问任意地址上的任意数据的，某些硬件平台只能再某些地址处取某些特定类型的数据，否则会出现硬件异常
//2.性能原因
//数据结构（尤其是栈）应该尽可能地再自然边界上对齐。原因在于，为了访问未对齐的内存，处理器需要作两次内存访问，而对齐的内存访问仅需要一次访问。
//假设一个处理器总是从内存中取8个字节，则地址必须是8的背书。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数，
//那么就可以用一次内存操作来读或者写值了。否则，我们可能需要执行两次内存访问，因为对象可能被分放再两个8字节内存中
//简而言之就是：牺牲空间（浪费空间） 来 换取时间


//那么在设计结构体的时候，我们既要满足对齐，又要节省空间，如何做呢？？
//尽量让占用空间小的成员集中在一起

//struct S1
//{
//	char c1;
//	int b;
//	char c2;
//};
//
//struct S2
//{
//	char c1;
//	char c2;
//	int b;
//};
//
//int main()
//{
//	printf("%zd\n", sizeof(struct S1));
//	printf("%zd\n", sizeof(struct S2));
//
//}






//修改默认对齐数
//#pragma 这个预处理指令，可以改变编译器的默认对齐数
//
//#pragma pack(1)//设置默认对齐数为1
//
//struct S
//{
//	char c1;
//	char c2;
//	int a;
//};
//#pragma pack()//取消设置的对齐数，还原为默认
//
//int main()
//{
//	printf("%zd", sizeof(struct S));//此时还是按照设置的默认值为1
//
//	return 0;
//}
//一般更改默认对齐数为2的幂次方



//结构体的传参

struct S
{
	int arr[1000];
	int n;
	double d;

};

//void print1(struct S temp)
//{
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", temp.arr[i]);
//	}
//	printf("\n");
//	printf("%d", temp.n);
//	printf("\n");
//	printf("%lf\n", temp.d);
//}
//
//void print2(const struct S* ps)
//{
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", ps->arr[i]);
//	}
//	printf("%d ", ps->n);
//	printf("%lf ", ps->d);
//}
//int main()
//{
//	struct S s = { {1,2,3,4,5}, 100, 3.14 };
//	print1(s);
//	print2(&s);
//	return 0;
//}

//上面传参肯定是Print2函数好一些
//原因：函数传参的时候，参数是需要压栈的，会在时间和空间上有较大的系统开销
//如果传递一个结构体对象的时候，结构体过大，参数压栈的系统开销也是比较大的，会导致性能下降

//结论：结构体传参，要传结构体的地址




